﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using SimpleData.Infrastructure.Provider;
using SimpleData.Infrastructure.Utils;
using SimpleData.ObjectModel.Collections;
using SimpleData.ObjectModel.Internals;
using SimpleData.Infrastructure.Validation;

namespace SimpleData.ObjectModel.Design
{
	/// <summary>
	/// Contexto de construção das regras
	/// </summary>
	public class ValidatorBuilder
	{
		private Rule currentRule = null;
		private FieldDescriptor currentField = null;
		private DocumentValidatorExpressionBuilder builder;
		


		public ValidatorBuilder()
			: this( null, null )
		{ }
	
		public ValidatorBuilder( IProvider<RuleName, IRuleBuilderFactory> factory, IProvider<FieldTypeName, FieldTypeDescriptor> types )
		{
			this.Rules = factory ?? Runtime.Rules;
			this.Types = types ?? Runtime.FieldTypes;
			//this.VarPrefix = "f";

			this.builder = new DocumentValidatorExpressionBuilder();
			this.builder.DeclareInput();
			this.builder.DeclareContext();
		}

		//public string VarPrefix
		//{
		//    get;
		//    private set;
		//}

		public ParameterExpression GetOrDeclareFieldVar( FieldDescriptor field )
		{
			Contract.Requires<ArgumentNullException>( field != null, "field" );

			ParameterExpression fieldVar = this.builder.GetFieldVar( field.Name );

			if ( fieldVar == null )
			{
				FieldTypeDescriptor fieldType = this.Types.Create( field.TypeName );
				var converter = fieldType.CreateConverter( field );

				fieldVar = this.builder.DeclareFieldVar( field.Name, converter.GetDotNetType() );
			}
			return fieldVar;
		}

		public IProvider<FieldTypeName, FieldTypeDescriptor> Types
		{ get; private set; }

		public IProvider<RuleName, IRuleBuilderFactory> Rules
		{ get; private set; }

		/// <summary>
		/// Para o processo de validação se não é mais válido
		/// </summary>
		/// <returns></returns>
		public ValidatorBuilder StopValidationIfInvalid()
		{
			throw new NotImplementedException();
		}
		
		public object IncludeScript( TextReader script )
		{
			throw new NotImplementedException();
		}

		public FieldValidatorBuilder<Object> Field()
		{
			return this.Field<Object>();
		}

		public FieldValidatorBuilder<TInput> Field<TInput>()
		{
			return new FieldValidatorBuilder<TInput>( this.currentField, this.currentRule, this.builder );
		}

		/// <summary>
		/// Inclui as regras para o campo <paramref name="field"/>.
		/// </summary>
		/// <param name="field"></param>
		/// <returns></returns>
		public ValidatorBuilder IncludeRule( FieldDescriptor field )
		{
			FieldDescriptor previousField = this.currentField;
			this.currentField = field;

			try
			{
				// -> input = document.Context.Values["..."];
				this.builder.AssignFieldToInput( field.Name );

				// -> if ( input is ... )
				this.IncludeRule( field.Rules.FieldType );

				// Realiza a validação, somente se for diferente de null
				ValidatorBuilder nested = new ValidatorBuilder( this.Rules, this.Types );
				nested.builder = new DocumentValidatorExpressionBuilder( this.builder.Input, this.builder.Context );
				nested.GetOrDeclareFieldVar( field );
				nested.IncludeRules( field.Rules );

				bool isRequired = false;
				// TODO : Verificar se pussui a regra para ser de preenchimento obrigatório
				//isRequired = field.Rules.Any(rule=>rule.Name.Equals( new RequiredRule().Name ));

				if ( isRequired )
					this.builder.If<Object>( input => input != null, nested.builder.Build(), /* adicionar mensagem dizendo que o campo é obrigatório */ null );
				else
					this.builder.If<Object>( input => input != null, nested.builder.Build(), null );

				return this;
			}
			finally
			{
				this.currentField = previousField;
			}
		}

		public ValidatorBuilder IncludeRule( Schema schemaRules )
		{
			this.IncludeRules( schemaRules.Rules );
			return this;
		}

		private void IncludeRules( IEnumerable<Rule> rules )
		{
			foreach ( var rule in rules )
				this.IncludeRule( rule );
		}

		private void IncludeRule( Rule rule )
		{
			Rule previousRule = this.currentRule;
			this.currentRule = rule;

			try
			{
				var factory = this.Rules.Create( rule.Name );
				factory.CreateRuleBuilder( rule ).BuildOn( this );
			}
			finally
			{
				this.currentRule = previousRule;
			}
		}

		/// <summary>
		/// Chamado pela factory do validator
		/// </summary>
		/// <param name="context"></param>
		/// <returns></returns>
		public IDocumentValidator Build()
		{

			/**
			 * Pseudo código para o validator
			 * 
			 * 1. A primeira validação será do tipo			=> Rule.Type.GetRuleBuilder()
			 * 1.1. Somente se não for NULL irá continuar o processamento
			 * 2. Somente se passou, irá fazer as demais validações
			 * 3. Ter o conceito de validações nested, que somente serão executadas se 
			 *		a primeira validação passou.
			 * 4. Declarar uma variável que seja convertida para o tipo definido
			 *		em Rule.Type
			 * 5. Ter métodos helper para adicionar a mensagem de validação padrão:
			 *		Exemplo: if(...) Rule.IsInvalid()
			 *				ou
			 *					if(...) Rule.IsInvalid("Isso é uma mensagem da regra.");
			 */

			return null;
		}
	}

	public sealed class FieldValidatorBuilder<TInput> 
	{

		internal FieldValidatorBuilder( FieldDescriptor field, Rule rule, DocumentValidatorExpressionBuilder documentBuilder )
		{
			this.Field = field;
			this.Rule = rule;
			this.DocumentBuilder = documentBuilder;
		}

		public DocumentValidatorExpressionBuilder DocumentBuilder
		{ get; private set; }

		public FieldDescriptor Field
		{ get; private set; }

		public Rule Rule
		{ get; private set; }

		public FieldValidatorBuilder<TInput> ValidIf( Expression<Func<TInput, bool>> condition )
		{
			var testExpression = condition.RemoveLambda();
			// Realiza a negação da condição
			var lambdaCondition = Expression.Lambda<Func<TInput, bool>>( Expression.Negate( testExpression ), condition.Parameters );

			return this.InvalidIf( lambdaCondition );
		}

		public FieldValidatorBuilder<TInput> InvalidIf( Expression<Func<TInput, bool>> condition )
		{
			var parameter = condition.Parameters.First();

			if ( parameter.Name != this.DocumentBuilder.Input.Name )	// Altera o parâmetro para a variável utilizada no input
				this.DocumentBuilder.If<TInput>( condition.Replace( parameter, this.DocumentBuilder.Input ), this.SendInvalidError(), null );
			else
				this.DocumentBuilder.If<TInput>( condition, this.SendInvalidError(), null );
			return this;
		}

		/// <summary>
		/// Cria a expressão com a chamada para incluir a mensagem de erro
		/// </summary>
		/// <returns></returns>
		private Expression SendInvalidError()
		{
			var fieldName = this.Field.Name;
			var displayName = this.Field.DisplayName;

			Expression<Func<DocumentValidatorContext, DocumentValidationResult.MessageItem>> sendError =
				context => ValidatorHelper.CreateMessage( context, fieldName, this.Rule.ErrorMessageFormat );

			return sendError.RemoveLambda().ResolveExternals( this.DocumentBuilder.Variables );
		}
	}

	public static class ValidatorHelper
	{

		public static EntityValidationResult.MessageItem CreateMessage(DocumentValidatorContext context, FieldName name, string message)
		{
			// TODO : adicionar mais parâmetros para a formatação da mensagem
			var item = new EntityValidationResult.MessageItem( name, message );
			context.Result.Errors.Add( item );
			return item;
		}

		public static void IsInvalid() 
		{
			//DocumentValidatorContext context;
			//context.AddRuleMessage( ( Rule )null, 0, "", "Field" );
		}

		public static void IsValid() { }
	}
}
